{*******************************************************************************
* The contents of this file are used with permission, subject to the Mozilla   *
* Public License Version 1.1 (the "License"); you may not use this file except *
* in compliance with the License. You may obtain a copy of the License at      *
* http://www.mozilla.org/MPL/                                                  *
*                                                                              *
* Software distributed under the License is distributed on an "AS IS" basis,   *
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for *
* the specific language governing rights and limitations under the License.    *
*                                                                              *
* The Original Code is distributed as part of the "mediate" product and is     *
* Copyright (C) @home media limited. All Rights Reserved.                      *
*                                                                              *
* Email: support@athomemedia.co.uk                                             *
* Web:   http://www.athomemedia.co.uk                                          *
*******************************************************************************}

unit DirectShowHelper;

interface

uses DirectShow9, ActiveX, SyncObjs;

const
  GRAPH_FILENAME_PREFIX = 'mediateDSGraph-';
  GRAPH_FILENAME_EXTENSION = '.grf';

type
  TAHMDirectShowGraph = class
  private
    FGraphEditID: Integer;
  protected
    FFilterGraph: IGraphBuilder;
  public
    constructor Create;
    destructor Destroy; override;
    property Graph: IGraphBuilder read FFilterGraph;
    procedure ShowInGraphEdit;
    procedure HideFromGraphEdit;
    function ValidateGraph: Boolean;
    procedure SaveGraph(Filename: WideString);
    procedure RenderMediaFile(Filename: WideString);
    function CreateFilter(const ClassID: TGUID): IBaseFilter;
    function AddFilter(Filter: IBaseFilter; Name: WideString): Boolean;
    function FindFilter(const ClassID: TGUID): IBaseFilter;
    function FindInterface(const IID: TGUID; out Obj): Boolean;
    function FindPinByName(Filter: IBaseFilter; Name: String): IPin;
    function FindPin(Filter: IBaseFilter; Direction: TPinDirection): IPin;
    function FindInputPin(Filter: IBaseFilter): IPin;
    function FindOutputPin(Filter: IBaseFilter): IPin;
    function RenderPin(Pin: IPin): Boolean;
  end;

// Miscellaneous DirectShow helper functions & procedures
function DSGetErrorString9(hr: HRESULT): string;

implementation

uses SysUtils, ApplicationManager, DXErr9, Logging, DSUtil;

//----------------------------------------------------------------------------//

function DSGetErrorString9(hr: HRESULT): string;
var
  Buffer: array[0..MAX_ERROR_TEXT_LEN] of char;
begin
  // Get maximum 255 characters of direct show error string
  AMGetErrorText(hr,@Buffer,MAX_ERROR_TEXT_LEN);
  Result := Buffer;
end;

//----------------------------------------------------------------------------//

constructor TAHMDirectShowGraph.Create;
var
  hr: HRESULT;
begin
  inherited Create;

  // Reset graph handle in running object table
  FGraphEditID := 0; //SM

  // Create filter graph manager instance
  hr := CoCreateInstance(CLSID_FilterGraph, nil, CLSCTX_INPROC_SERVER, IID_IGraphBuilder, FFilterGraph);
  if hr <> S_OK then
    raise EDirectShowException.Create('Failed to create DirectShow filter graph builder: ' + DSGetErrorString9(hr));
end;

destructor TAHMDirectShowGraph.Destroy;
begin
  // Ensure graph is removed from ROT
  HideFromGraphEdit;

  // Release filter graph handle
  FFilterGraph := nil;

  inherited Destroy;
end;

procedure TAHMDirectShowGraph.ShowInGraphEdit;
begin
  // If preference option for saving graphs is on, add graph to running object
  // table so that it can be seen in graphedit - this is just useful for dev.
  if ApplicationManagerInstance.PreferencesManager.SaveGraphToFile then
  begin
    if FGraphEditID = 0 then
      AddGraphToRot(IFilterGraph2(FFilterGraph) , FGraphEditID);
  end;
end;

procedure TAHMDirectShowGraph.HideFromGraphEdit;
begin
  // Remove graph from running object table if it was previously added
  if FGraphEditID <> 0 then
  begin
    RemoveGraphFromRot(FGraphEditID);
    FGraphEditID := 0;
  end;
end;

function TAHMDirectShowGraph.ValidateGraph: Boolean;
begin
//TODO: ValidateGraph not implemented
  Result := True;
end;

procedure TAHMDirectShowGraph.SaveGraph(Filename: WideString);
var
  GraphFilename: WideString;
  hr: HRESULT;
begin
  // Only save graph if preferences indicate we should do
  if ApplicationManagerInstance.PreferencesManager.SaveGraphToFile then
  begin
    // Build graph filename
    GraphFilename := LoggingInstance.LogDirectory + GRAPH_FILENAME_PREFIX +
                     ChangeFileExt(Filename, GRAPH_FILENAME_EXTENSION);

    // Use handy function in DSUtil to save graph to disk
    hr := SaveGraphFile(FFilterGraph, GraphFilename);
    if hr <> S_OK then
      LogWarn('Failed to save DirectShow graph to ' + GraphFilename +
              ', error: ' + DSGetErrorString9(hr));
  end;
end;

procedure TAHMDirectShowGraph.RenderMediaFile(Filename: WideString);
var
  hr: HRESULT;
begin
  LogInfo('Added default filters to DirectShow graph, now rendering file');
  hr := FFilterGraph.RenderFile(PWideChar(Filename), nil);
  if hr <> S_OK then
    raise EDirectShowException.Create('Failed to render DirectShow file, error: ' + DSGetErrorString9(hr));
end;

function TAHMDirectShowGraph.CreateFilter(const ClassID: TGUID): IBaseFilter;
var
  hr: HRESULT;
  Filter: IBaseFilter;
begin
  // Set default result - couldn't create filter
  Result := nil;

  hr := CoCreateInstance(ClassID, nil, CLSCTX_INPROC_SERVER, IID_IBaseFilter, Filter);
  if hr <> S_OK then
    LogInfo('Failed to create DirectShow filter, error: ' + DSGetErrorString9(hr))
  else
    Result := Filter;
end;

function TAHMDirectShowGraph.AddFilter(Filter: IBaseFilter; Name: WideString): Boolean;
var
  hr: HRESULT;
begin
  // Set default result - couldn't add filter
  Result := False;

  // Add passed filter to graph
  hr := FFilterGraph.AddFilter(Filter, PWideChar(Name));
  if (hr = S_OK) or (hr = VFW_S_DUPLICATE_NAME) then
    Result := True
  else
    LogInfo('Failed to add DirectShow ' + Name + ' filter to graph: ' + DSGetErrorString9(hr));
end;

function TAHMDirectShowGraph.FindFilter(const ClassID: TGUID): IBaseFilter;
var
  Enum: IEnumFilters;
  Filter: IBaseFilter;
  FilterClass: TGUID;
  hr: HRESULT;
begin
  Result := nil;

  // Get filter enumerator for our graph
  hr := FFilterGraph.EnumFilters(Enum);
  if hr <> S_OK then
  begin
    LogWarn('Failed to create enumerator for DirectShow filter graph');
    Exit;
  end;

  // Enumerate all filters in the graph
  while Enum.Next(1, Filter, nil) = S_OK do
  begin
    // Check for matching class id
    if (Filter.GetClassID(FilterClass) = S_OK) and IsEqualGUID(ClassID, FilterClass) then
    begin
      // Success, we found a matching filter
      Result := Filter;
      Exit;
    end;
  end;
end;

function TAHMDirectShowGraph.FindInterface(const IID: TGUID; out Obj): Boolean;
begin
  // Query our filter graph COM object for required interface
  Result := (FFilterGraph.QueryInterface(IID, Obj) = S_OK);
end;

function TAHMDirectShowGraph.FindPinByName(Filter: IBaseFilter; Name: String): IPin;
var
  hr: HRESULT;
  EnumPins: IEnumPins;
  FoundPin: IPin;
  PinInfo: TPinInfo;
begin
  // Default result - pin not found
  Result := nil;

  // Validate filter
  if Filter <> nil then
  begin
    // Get an enumeration of all filter pins
    hr := Filter.EnumPins(EnumPins);
    if hr <> S_OK then
    begin
      LogWarn('Failed to enumerate pins on filter: ' + DSGetErrorString9(hr));
      Exit;
    end;

    // Step through pin enumeration looking for matching pin direction
    while EnumPins.Next(1, FoundPin, nil) = S_OK do
    begin
      // Get detailed information for this pin
      hr := FoundPin.QueryPinInfo(PinInfo);
      if hr <> S_OK then
      begin
        LogWarn('Failed to determine pin information on filter pin: ' + DSGetErrorString9(hr));
        Exit;
      end;

      // Does pin name matchIs this a pin of required direction?
      if PinInfo.achName = Name then
      begin
        Result := FoundPin;
        Exit;
      end;
    end;
  end
  else
    raise EDirectShowException.Create('DirectShow error - nil filter specified for FindPin');
end;

function TAHMDirectShowGraph.FindPin(Filter: IBaseFilter; Direction: TPinDirection): IPin;
var
  hr: HRESULT;
  EnumPins: IEnumPins;
  FoundPin: IPin;
  PinDir: TPinDirection;
begin
  // Default result - pin not found
  Result := nil;

  // Validate filter
  if Filter <> nil then
  begin
    // Get an enumeration of all filter pins
    hr := Filter.EnumPins(EnumPins);
    if hr <> S_OK then
    begin
      LogWarn('Failed to enumerate pins on filter: ' + DSGetErrorString9(hr));
      Exit;
    end;

    // Step through pin enumeration looking for matching pin direction
    while (EnumPins.Next(1, FoundPin, nil) = S_OK) do
    begin
      // What direction is this pin?
      hr := FoundPin.QueryDirection(PinDir);
      if hr <> S_OK then
      begin
        LogWarn('Failed to determine pin direction on filter pin: ' + DSGetErrorString9(hr));
        Exit;
      end;

      // Is this a pin of required direction?
      if PinDir = Direction then
      begin
        Result := FoundPin;
        Exit;
      end;
    end;
  end
  else
    raise EDirectShowException.Create('DirectShow error - nil filter specified for FindPin');
end;

function TAHMDirectShowGraph.FindInputPin(Filter: IBaseFilter): IPin;
begin
  // Use above method to find input pin
  Result := FindPin(Filter, PINDIR_INPUT);
end;

function TAHMDirectShowGraph.FindOutputPin(Filter: IBaseFilter): IPin;
begin
  // Use above method to find output pin
  Result := FindPin(Filter, PINDIR_OUTPUT);
end;

function TAHMDirectShowGraph.RenderPin(Pin: IPin): Boolean;
var
  hr: HRESULT;
begin
  // Set default result - failed to render
  Result := False;

  // Ask graph builder to render pin using existing filters in graph
  hr := FFilterGraph.Render(Pin);
  if hr <> S_OK then
    LogWarn('Failed to render output pin in DirectShow graph: ' + DSGetErrorString9(hr))
  else
    Result := True;
end;

end.
